﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Devices;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Data;

namespace RayDen.RayEngine.IntersectionEngines
{

    public class SpherePlaneScene
    {
        public Vector SphereCenter;
        public float SphereRadius;
        public Normal PlaneNormal;
        public float PlaneDistance;
    }

    public class SpherePlaneIntersectionDevice : IntersectionEngine
    {
        public const float BlockMinEps = 0.001f;
        public const float HitMinEps = 0.0001f;

        protected ConcurrentQueue<Tuple<int, RayBuffer>> todoRayBuffers;
        protected List<ConcurrentQueue<RayBuffer>> doneRayBuffers;
        public SpherePlaneScene GeoScene;
        protected double statsDeviceIdleTime;
        protected double statsDeviceTotalTime;
        protected Stopwatch wallclock;
        protected Task renderingTask;
        protected CancellationTokenSource cancelToken;
        public SpherePlaneIntersectionDevice(RayEngineScene scene, SpherePlaneScene geoScene)
            : base(scene)
        {
            this.GeoScene = geoScene;
        }

        public override void Trace(RayBuffer buffer)
        {


        }

        public override void PushRayBuffer(RayBuffer rayBuffer, int clientIdx)
        {
            if (clientIdx + 1 > doneRayBuffers.Count)
            {
                while (doneRayBuffers.Count < (clientIdx + 1))
                {
                    doneRayBuffers.Add(new ConcurrentQueue<RayBuffer>());
                }
            }
            todoRayBuffers.Enqueue(new Tuple<int, RayBuffer>(clientIdx, rayBuffer));
        }

        public override RayBuffer PopRayBuffer(int info)
        {
            RayBuffer result;
            doneRayBuffers[info].TryDequeue(out result);
            return result;
        }
        public override void Stop()
        {
            wallclock.Stop();
            started = false;

            if (renderingTask.Status == TaskStatus.Running)
            {
                cancelToken.Cancel();
                renderingTask.Dispose();
                renderingTask = null;
            }

            this.todoRayBuffers = new ConcurrentQueue<Tuple<int, RayBuffer>>();
            this.doneRayBuffers = new List<ConcurrentQueue<RayBuffer>>() { { new ConcurrentQueue<RayBuffer>() } };
        }

        public void Interrupt()
        {
            wallclock.Stop();
            if (this.renderingTask.Status == TaskStatus.Running)
            {
                cancelToken.Cancel();
            }
        }

        public override void Start()
        {
            if (started)
                return;
            started = true;
            wallclock.Start();
            statsDeviceIdleTime = 0.0;
            statsDeviceTotalTime = 0.0;
            cancelToken = new CancellationTokenSource();
            this.renderingTask = Task.Factory.StartNew(() => RayIntersection(cancelToken.Token, this), cancelToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
        }

        protected static void RayIntersection(CancellationToken ct, SpherePlaneIntersectionDevice device)
        {
#if VERBOSE
            try
            {
                
#endif
            Tracer.TraceLine("Intersection Device {0}", device.GetType().Name);
            while (!ct.IsCancellationRequested)
            {
                //var t1 = 0;// device.wallclock.ElapsedTicks;
                Tuple<int, RayBuffer> rayBuffer;

#if OLD 
                if (!device.todoRayBuffers.TryDequeue(out rayBuffer))
                    continue;

#else
                if (!device.todoRayBuffers.TryDequeue(out rayBuffer))
                {
                    continue;
                }
#endif

                //var t2 = DateTime.UtcNow.Ticks;
                device.Trace(rayBuffer.Item2);
                //Tracer.TraceLine("Result returns {0} items ", buffer.rayHits.Length);
                //            Tracer.TraceLine("Intersections {0} items ", buffer.rayHits.Count(p => p.index != uint.MaxValue));
                //var t3 = device.wallclock.ElapsedTicks;

                //device.statsDeviceIdleTime += t2 - t1;
                //device.statsDeviceTotalTime += t3 - t1;
                device.statsTotalRayCount += rayBuffer.Item2.RaysInfo.Length;
                device.doneRayBuffers[rayBuffer.Item1].Enqueue(rayBuffer.Item2);
            }

#if VERBOSE
            }
            catch (Exception ex)
            {
                Tracer.TraceLine("Intersection device exception");
                //Tracer.TraceLine("Error triangle {0}", currentTriangleIndex);
                Tracer.TraceLine(ex.Message);
                Tracer.TraceLine(ex.StackTrace);

            }
#endif
        }

        protected static bool IntersectSphere(ref Point v, float r, ref RayData ray,
                                              ref RayHit rayHit)
        {
            float a = ray.Dir.x * ray.Dir.x +
                ray.Dir.y * ray.Dir.y +
                ray.Dir.z * ray.Dir.z;
            float b = 2 * (ray.Dir.x * (ray.Org.x - v.x) +
                ray.Dir.y * (ray.Org.y - v.y) +
                ray.Dir.z * (ray.Org.z - v.z));
            float c = (ray.Org.x - v.x) * (ray.Org.x - v.x) +
                (ray.Org.y - v.y) * (ray.Org.y - v.y) +
                (ray.Org.z - v.z) * (ray.Org.z - v.z) - r * r;

            float delta = b * b - 4 * a * c;

            if (delta > 0)
            {
                float x1 = (-b - MathLab.Sqrt(delta)) / (2 * a);
                float x2 = (b - MathLab.Sqrt(delta)) / (2 * a);


                if (x1 > HitMinEps)
                {
                    rayHit.Index = 1;
                    rayHit.Distance = x1;

                    rayHit.U = x1;
                    rayHit.V = x2;
                    return true;
                }
            }
            rayHit.Index = RayBuffer.NotHit;
            return false;
        }

        protected static bool IntersectSphere(ref Normal N, float D, ref RayData ray, ref RayHit rayHit)
        {
            var vd = N.ToVec() & ray.Dir;

            if (vd > -MathLab.Epsilon && vd < MathLab.Epsilon)
            {
               rayHit.Index = RayBuffer.NotHit;
                return false;
            }

            var t = -((N.ToVec() & ray.Org.ToVec()) + D) / vd;

            var result = t > MathLab.Epsilon;
            if (result)
            {
                rayHit.Index = 0;
                rayHit.Distance = t;
            }
            rayHit.Index = RayBuffer.NotHit;
            return false;
        }



    }
}
